//
// Created by amon.huang on 2021/7/25.
//

#include "Audio.h"
#include "MusicConstDefine.h"

Audio::Audio(int audioStreamIndex, JNICall *pJniCall, PlayerStatus *pPlayerStatus) :
        MediaBase(audioStreamIndex, pJniCall, pPlayerStatus),
        seekMutex(), sampleRate(44100), shouldStop(false), playThreadT(0) {
    pthread_mutex_init(&seekMutex, nullptr);
}

Audio::~Audio() {
    release();
}

void Audio::release() {
    pPlayerStatus->isExit = true;
    if (playThreadT != 0) {
        pthread_join(playThreadT, nullptr);
        playThreadT = 0;
    }
    shouldStop = true;
    MediaBase::release();
    if (pSwrContext) {
        swr_free(&pSwrContext);
        free(pSwrContext);
        pSwrContext = nullptr;
    }
    if (resampleOutBuffer) {
        free(resampleOutBuffer);
    }
    if (this->pPlayItf) {
        (*this->pPlayItf)->SetPlayState(pPlayItf, SL_PLAYSTATE_STOPPED);
    }

    if ((*((uint32_t *)&seekMutex) & 0x0000ffff) != 0x0000ffff) {
        pthread_mutex_destroy(&seekMutex);
    }
}

void *threadAudioPlay(void *context) {
    auto *audio = (Audio *) context;
    audio->createOpenSLES();
    return nullptr;
}

void Audio::pause() {
    if (this->pPlayItf) {
        (*this->pPlayItf)->SetPlayState(pPlayItf, SL_PLAYSTATE_PAUSED);
    }
}

void Audio::play() {
    pthread_create(&playThreadT, nullptr, threadAudioPlay, this);
}

int Audio::resampleAudio() {
    AVPacket *pPacket = nullptr;
    AVFrame *pFrame = av_frame_alloc();
    int dataSize = 0;
    while (pPlayerStatus != nullptr && !pPlayerStatus->isExit) {
        pPacket = pPackQueue->pop();
        if (pPacket == nullptr) {
            continue;
        }
        int codecSendPacketRes = avcodec_send_packet(pCodecContext, pPacket);
        if (codecSendPacketRes == 0) {
            int codecReceiveFrameRes = avcodec_receive_frame(pCodecContext, pFrame);
            if (codecReceiveFrameRes == 0) {
                dataSize = swr_convert(pSwrContext, &resampleOutBuffer, pFrame->nb_samples,
                                       (const uint8_t **) pFrame->data, pFrame->nb_samples);
                dataSize = dataSize * 2 * 2;
                double times = av_frame_get_best_effort_timestamp(pFrame) * av_q2d(timeBase);
                if (times > currentTime) {
                    currentTime = times;
                }
                break;
            }
        }
        av_packet_unref(pPacket);
        av_frame_unref(pFrame);
    }
    av_packet_free(&pPacket);
    av_frame_free(&pFrame);
    return dataSize;
}

void playerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {
    auto *pAudio = static_cast<Audio *>(context);
    if (pAudio) {
        if (pAudio->shouldStop) {
            return;
        }
        int dataSize = pAudio->resampleAudio();
        if (dataSize > 0) {
            pAudio->currentTime += dataSize / ((double) (pAudio->sampleRate * 2 * 2));
            if (pAudio->currentTime - pAudio->lastUpdateTime > 1) {
                pAudio->lastUpdateTime = pAudio->currentTime;
                if (pAudio->pJniCall) {
                    pAudio->pJniCall->callPlayProgress(THREAD_CHILD, pAudio->currentTime,
                                                       pAudio->duration);
                }
            }
            if (pAudio->duration > 0 && pAudio->duration <= pAudio->currentTime) {
                pAudio->pJniCall->callPlayComplete(THREAD_CHILD);
            }
        }
        pAudio->pJniCall->callPcmData(THREAD_CHILD, pAudio->resampleOutBuffer, dataSize);
        (*bq)->Enqueue(bq, pAudio->resampleOutBuffer, dataSize);
    }
}

void Audio::createOpenSLES() {
    SLresult result;
    SLObjectItf engineObject = nullptr;
    SLEngineItf engineEngine = nullptr;
    slCreateEngine(&engineObject, 0, nullptr, 0, nullptr, nullptr);
    (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);

    SLObjectItf pMix;
    SLuint32 numInterfaces = 1;
    const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB};
    const SLboolean req[1] = {SL_BOOLEAN_FALSE};
    SLEnvironmentalReverbItf mixEnvironmental = nullptr;
    result = (*engineEngine)->CreateOutputMix(engineEngine, &pMix, numInterfaces, ids, req);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;

    result = (*pMix)->Realize(pMix, SL_BOOLEAN_FALSE);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;

    result = (*pMix)->GetInterface(pMix, SL_IID_ENVIRONMENTALREVERB, &mixEnvironmental);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;

    const SLEnvironmentalReverbSettings settings = SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR;
    result = (*mixEnvironmental)->SetEnvironmentalReverbProperties(mixEnvironmental, &settings);
    (void) result;

    SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
    SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1,
                                   SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
                                   SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
                                   SL_BYTEORDER_LITTLEENDIAN};
    SLObjectItf pPlayer = nullptr;
    SLPlayItf pPlayItfL = nullptr;
    SLDataSource audioSrc = {&loc_bufq, &format_pcm};
    SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, pMix};
    SLDataSink audioSnk = {&loc_outmix, nullptr};
    const SLInterfaceID interfaceIds[3] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME, SL_IID_PLAYBACKRATE};
    const SLboolean interfaceRequired[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
    result = (*engineEngine)->CreateAudioPlayer(engineEngine, &pPlayer, &audioSrc, &audioSnk, 3,
                                                interfaceIds, interfaceRequired);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;
    result = (*pPlayer)->Realize(pPlayer, SL_BOOLEAN_FALSE);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;
    result = (*pPlayer)->GetInterface(pPlayer, SL_IID_PLAY, &pPlayItfL);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;

    this->pPlayItf = pPlayItfL;

    SLAndroidSimpleBufferQueueItf playerBufferQueue;
    result = (*pPlayer)->GetInterface(pPlayer, SL_IID_BUFFERQUEUE, &playerBufferQueue);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;

    result = (*playerBufferQueue)->RegisterCallback(playerBufferQueue, playerCallback, this);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;

    (*pPlayItf)->SetPlayState(pPlayItf, SL_PLAYSTATE_PLAYING);
    playerCallback(playerBufferQueue, this);
}

void Audio::seek(uint64_t seconds) {
    if (duration <= 0) {
        return;
    }

    if (seconds > 0 && seconds < duration) {
        pthread_mutex_lock(&seekMutex);
        pPackQueue->clear();
        lastUpdateTime = 0;
        currentTime = 0;
        int64_t rel = seconds * AV_TIME_BASE;
        //av_seek_frame(pFormatContext, -1, rel, AVSEEK_FLAG_BACKWARD);
        avcodec_flush_buffers(pCodecContext);
        pthread_mutex_unlock(&seekMutex);
    }
}

void Audio::analysisStreamInner(ThreadMode threadMode, AVFormatContext *pFormatContext) {
    sampleRate = pCodecContext->sample_rate;
    LOGE("%d, %d", pCodecContext->sample_rate, pCodecContext->channels);

    resampleOutBuffer = (uint8_t *) malloc(pCodecContext->frame_size * 2 * 2);

    //--------重采样 start --------
    int64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
    AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
    int64_t out_sample_rate = AUDIO_SAMPLE_RATE;
    int64_t in_ch_layout = pCodecContext->channel_layout;
    AVSampleFormat in_sample_fmt = pCodecContext->sample_fmt;
    int64_t in_sample_rate = pCodecContext->sample_rate;
    pSwrContext = swr_alloc_set_opts(nullptr, out_ch_layout, out_sample_fmt,
                                     out_sample_rate, in_ch_layout, in_sample_fmt,
                                     in_sample_rate, 0,
                                     nullptr);
    if (pSwrContext == nullptr) {
        callPlayJniError(threadMode, -1, "resample context error");
        return;
    }
    int swrInitRes = swr_init(pSwrContext);
    if (swrInitRes < 0) {
        callPlayJniError(threadMode, -1, "swrInitRes error");
        return;
    }
    pJniCall->callMusicInfo(threadMode, AUDIO_SAMPLE_RATE, 2);
}

